#! /usr/bin/env python
# -*- coding: utf8 -*-

# Alexandre Coster <acoster@rhrk.uni-kl.de>
# AG DBIS - Technische Universität Kaiserslautern

"""
This files contains the classes C{config} and C{MessyXML} that are, respectively, the class that reads a configuration file, and instatiates
all objects that are represented on it, and the class that represents the program (that is, which controls the flow of operations).
"""

import os
import sys
import os.path
import copy

# Keeps things working in case we are on Python 2.4, and cElementTree is installed.
if sys.version_info[0:2] == (2, 5):
    from xml.etree import cElementTree as et
else:
    import cElementTree as et

# Project imports
import load
import prob
import trans

class config:
    """
    This class represents an configuration file. That is, given a configuration file, will parse it and instantiate all objects from it, i.e.,
    will read each section of the configuration file, and call the adequate constructors, storing all these objects on private dictionary, whose
    copies can be accessed by properties.
    """

    def __init__(self, fname):
        """
        Parses the XML configuration file pointed by C{fname} and instantiates all objects.

        @parameter fname: Path to the configuration file.
        @type fname: str or unicode.
        """
        self.__algorithms      = {}
        self.__probDistr       = {}
        self.__tags            = []
        self.__transformations = {}

        self.__addIDs          = None
        self.__root            = None

        # parses the file
        self.__confRoot = et.parse(fname).getroot()

        # searches for distribution configurations
        for i in self.__confRoot.findall("./distributions/distribution"):
            name                   = i.find("name").text.strip()
            self.__probDistr[name] = prob.probDistr(i)

        # calls the function that will load all algorithm "plug-ins"
        algorithms = self.__load_algorithms()

        # searches for algorithm configurations
        for i in self.__confRoot.findall("./algorithms/algorithm"):
            name = i.find("name").text.strip()
            base = i.find("base").text.strip().lower()
            self.__algorithms[name] = algorithms[base](i)

        # searches for transformations
        for i in self.__confRoot.findall("./transformations/transformation"):
            name = i.find("name").text.strip()
            if i.find("isRoot") != None:
                self.__root = trans.root(i, self.__probDistr)
                self.__root_tag = name
            else:
                self.__transformations[name] = trans.transform(i, self.__algorithms)

        if not self.__root:
            print "(config error) A root should be set."
            sys.exit(1)

        self.__addIDs  = (self.__confRoot.find("./options/addIDs") != None)
        self.__tags    =  self.__transformations.keys()

    def __load_algorithms(self):
        """
        @return: Dictionary containing all algorithms.

        Loads all algorithms on the C{algo/} directory, and returns them in a dictionary, where the keys are the algorithms names, as
        defined on their C{name} property.
        """

        # little hack to get the directory of this file.
        path =  "/".join(os.path.abspath(sys.modules[__name__].__file__).split("/")[:-1]) + "/algo"
        return dict([(x.name.lower(), x) for x in load.loadPlugins(path)])

    @property
    def addIDs(self):
        """
        C{True} if the Add IDs option is enabled, and C{False} otherwise.

        @type: bool
        """
        return self.__addIDs

    @property
    def algorithms(self):
        """
        Dictionary containing all algorithms.

        @type: dict
        """
        return copy.copy(self.__algorithms)

    @property
    def probDistr(self):
        """
        Dictionary containing all probability distributions.

        @type: dict
        """
        return copy.copy(self.__probDistr)

    @property
    def root(self):
        """
        Transformation object for the root node.

        @type: root
        """
        return self.__root

    @property
    def root_tag(self):
        """
        Name of the node configured as root.

        @type: str
        """
        return self.__root_tag

    @property
    def tags(self):
        """
        List containing all configured tag names.

        @type: list
        """
        return self.__tags[::]

    @property
    def transformations(self):
        """
        Dictionary containing all transformations (except the root). The keys are the tag names.

        @type: dict
        """
        return self.__transformations

class MessyXML:
    """
    This class is the "main" body of the mXML application.
    """

    def __init__(self, config_file, xml_file, outfile = ""):
        """
        Instantiates the object that will be the main program body.

        @parameter config_file: Path to the configuration file.
        @parameter xml_file: Path to the file that will be transformed.
        @parameter outfile: Path to the file in which the statistics (ammount of elements and ammount of injected errors per subtree) will be
        written. An empty string means that this feature will be disabled, and C{stdout} or C{stdin} means that those default streams should
        be used.
        @type config_file: str
        @type xml_file: str
        @type outfile: str
        """

        self.__conf  = config(config_file)
        self.__fname = xml_file
        self.__tree  = et.parse(xml_file)

        self.__transforms = self.__conf.transformations
        self.__algorithms = self.__conf.algorithms
        self.__probDistr  = self.__conf.probDistr

        if outfile == "":
            self.__outfile = None
        else:
            if outfile in ["stdout", "stderr"]:
                self.__outfile = getattr(sys, outfile)
            else:
                self.__outfile = open(outfile, "w")

    def run(self, fnames = []):
        """
        Start the transformations, generating a new XML file for each file in C{fnames}.

        @param fnames: Paths to the output files.
        @type fnames: list
        @rtype: NoneType
        """
        c         = 0
        results   = []
        origRoot  = self.__tree.getroot()


        nodes = origRoot.findall(".//%s" % (self.__conf.root_tag,))
        if len(fnames) > 1:
            results = self.__conf.root(nodes = nodes, transforms = self.__transforms,
                                                          addIDs = self.__conf.addIDs, resultAmmount = len(fnames))
        else:
            results = self.__conf.root(nodes = nodes, transforms = self.__transforms, outFile = self.__outfile,
                                                          addIDs = self.__conf.addIDs, resultAmmount = len(fnames))

        if self.__outfile not in (sys.stderr, sys.stdout, None):
            self.__outfile.close()

        tuples  = [(results[i], fnames[i]) for i in range(len(fnames))]

        if self.__conf.addIDs:
            fp = file("%s-withIDs.xml" % (self.__fname[:-4],), "w")
            self.__tree.write(fp)
            fp.close()

        for i in tuples:
            newRoot = et.Element(origRoot.tag)
            newTree = et.ElementTree(newRoot)

            for j in i[0]:
                newRoot.append(j)

            newTree.write(file=i[1])
